Skip to content

Conversation

@niko-exo
Copy link

@niko-exo niko-exo commented Jan 12, 2026

ref: https://app.clickup.com/t/86b82fa34

86b82fa34 - feat: add url fragment linking to tabs on edit sponsor page

Changelog

  • Add helper functions to handle the relationship between tabs and the url fragment.
  • Add react hook to listen for every fragment change.
  • Update fragment on every tab change.
  • Set default page on the tab general.
  • Set that if an user enters some random fragment, it will be redirected to the general tab.

Links

86b82fa34 - Add TAB deep-linking using URL fragment

Evidence

2026-01-12_09-56-27.mp4

Summary by CodeRabbit

  • New Features

    • Sponsors page tabs now use URL hash fragments for direct links/bookmarks; tab selection stays synced with the browser hash and updates the URL when changed.
  • Tests

    • Added comprehensive tests for tab↔fragment mappings, hash-driven tab selection, and UI behavior when hashes change.
  • Chores

    • Exposed tab/fragment helper utilities, a tab panel component, and some default state values to improve testability and reuse.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Maps EditSponsorPage tabs to URL hash fragments, initializes and syncs selected tab from window.location.hash, updates the hash on tab changes, exports helpers (tabsToFragmentMap, getFragmentFromValue, getTabFromUrlFragment) and CustomTabPanel, adds tests for fragment behavior, and exposes DEFAULT_STATE from two reducers.

Changes

Cohort / File(s) Summary
Tab URL Persistence
src/pages/sponsors/edit-sponsor-page.js
Added tabsToFragmentMap and exported helpers getFragmentFromValue() and getTabFromUrlFragment(). Switched initial tab selection to use URL hash, updated on-tab-change to set window.location.hash, added a hashchange listener to resync state, and exported CustomTabPanel (adds data-testid).
Tests for hash-driven tabs
src/pages/sponsors/__tests__/edit-sponsor-page.test.js
New test suite covering getFragmentFromValue and getTabFromUrlFragment; tests that clicking tabs updates window.location.hash and that changing the hash updates rendered tab panels; includes mocks and Redux test state.
Reducer exports
src/reducers/sponsors/sponsor-reducer.js, src/reducers/summits/current-summit-reducer.js
Changed DEFAULT_STATE declarations to named exports (export const DEFAULT_STATE = { ... }) in both files (no shape/logic change).

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant EditSponsorPage
    participant TabState

    Note over Browser,EditSponsorPage: Initial page load
    Browser->>EditSponsorPage: load URL (may include `#fragment`)
    EditSponsorPage->>EditSponsorPage: getTabFromUrlFragment()
    EditSponsorPage->>TabState: set selectedTab

    Note over Browser,EditSponsorPage: User selects a tab
    Browser->>EditSponsorPage: click tab
    EditSponsorPage->>EditSponsorPage: onTabChange -> getFragmentFromValue()
    EditSponsorPage->>Browser: set window.location.hash
    EditSponsorPage->>TabState: update selectedTab

    Note over Browser,EditSponsorPage: External hash change (e.g., back/forward)
    Browser->>EditSponsorPage: window.location.hash changes
    EditSponsorPage->>EditSponsorPage: hashchange listener -> getTabFromUrlFragment()
    EditSponsorPage->>TabState: resync selectedTab
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat: sponsor cart view #748 — Modifies src/pages/sponsors/edit-sponsor-page.js (adds SponsorCart tab); likely to affect tab indices/ordering.

Suggested reviewers

  • smarcet

Poem

🐇 I hopped through hashes, leaving tiny tracks,
Tabs chase my footprints, forward and back.
Click, bookmark, or tumble through time’s art,
I stitched each fragment — a small rabbit’s heart. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding URL fragment linking to tabs on the edit sponsor page, which aligns with all file modifications in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/pages/sponsors/edit-sponsor-page.js:
- Around line 116-118: The useEffect using window.location.hash in the
dependency array won't re-run on hash changes; replace it with a hashchange
event listener: in the effect that currently calls
setSelectedTab(getTabFromUrlFragment()), register
window.addEventListener('hashchange', handler) where handler calls
setSelectedTab(getTabFromUrlFragment()), call the handler once on mount to
initialize, and remove the listener in the cleanup; remove window.location.hash
from the dependency array (use []).
🧹 Nitpick comments (1)
src/pages/sponsors/edit-sponsor-page.js (1)

46-64: Helper functions look good, consider consolidating the tab definition.

The mapping and helper functions are well-structured. The fallback to index 0 for unrecognized fragments correctly implements the PR requirement.

Consider deriving tabsToFragmentMap from the tabs array (or vice versa) to avoid maintaining two separate lists that must stay in sync. For now, this is acceptable.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9ae9ce and 22f0d4b.

📒 Files selected for processing (1)
  • src/pages/sponsors/edit-sponsor-page.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (2)
src/pages/sponsors/edit-sponsor-page.js (2)

120-123: LGTM!

Setting window.location.hash correctly updates the URL and adds a browser history entry, enabling back/forward navigation between tabs.


155-155: Formatting change acknowledged.

Minor whitespace adjustment in the sx prop—no functional impact.

@fntechgit fntechgit deleted a comment from coderabbitai bot Jan 12, 2026
Copy link

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@niko-exo please review

const result = tabsToFragmentMap.indexOf(
window.location.hash.replace("#", "")
);
return result > -1 ? result : 0;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the initial load
general tab should be selected hence #general should be show by default

 if (result > -1) return result;

  if (window.location.hash) window.location.hash = "general";
  return 0
```;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! If the general fragment is provided, then it will use it. If not it will retrieve 0, which means use the general fragment. Note that the general fragment is included on the 0 position of the array above the function. I think this function is performing what you mean.

const [selectedTab, setSelectedTab] = useState(0);
const [selectedTab, setSelectedTab] = useState(getTabFromUrlFragment());

useEffect(() => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@niko-exo
rite now the tab selection only updates from the URL hash on initial page load. The effect useEffect(..., [window.location.hash]) won’t reliably fire on hash updates because changing window.location.hash doesn’t trigger a React re-render.

To support SPA-style hash navigation (manual hash edits, back/forward), please replace that effect with a hashchange listener

@niko-exo niko-exo force-pushed the feature/tab-deep-linking-fragment branch from 22f0d4b to 9c4adf3 Compare January 14, 2026 16:51
@niko-exo
Copy link
Author

#756 Unblocks this pull request.

@niko-exo niko-exo added the BLOCKED Pull request blocked by other ticket label Jan 15, 2026
@niko-exo niko-exo force-pushed the feature/tab-deep-linking-fragment branch from 9c4adf3 to c3e31c6 Compare January 16, 2026 12:12
@niko-exo niko-exo removed the BLOCKED Pull request blocked by other ticket label Jan 16, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/pages/sponsors/__tests__/edit-sponsor-page.test.js`:
- Around line 144-151: The test's assertion expects window.location.hash without
the leading '#' but JSDOM stores hashes with the '#' prefix; update the
assertion in src/pages/sponsors/__tests__/edit-sponsor-page.test.js to expect
"#forms" instead of "forms" (the component sets the fragment via
getFragmentFromValue() in edit-sponsor-page.js which returns values without '#',
but window.location.hash will include it). Ensure any other assertions in this
test that check window.location.hash are adjusted the same way.
- Line 17: Remove the global.window override that stubs the entire JSDOM window
(the line `global.window = { location: { pathname: "/sponsor-forms/items" } };`)
so tests use the real JSDOM window and its Location API; rely on existing test
setup that uses Object.defineProperty to modify window.location or
window.location.hash when needed, and ensure no other tests expect the stubbed
object.
- Around line 153-217: The test currently only checks existence of tab panels
which is a false positive because CustomTabPanel renders all panels
hidden/visible; update the test to wait for the component to react to the hash
change (use waitFor or findByTestId) and assert visibility instead of mere
existence: after setting location.hash="#general" wait for simple-tabpanel-0 to
be visible (expect(...).toBeVisible()) and that simple-tabpanel-1 is hidden
(expect(...).not.toBeVisible() or expect(...).toHaveAttribute('hidden')); then
after setting hash="#users" wait for simple-tabpanel-1 to be visible and assert
simple-tabpanel-0 is hidden. Use screen.findByTestId or waitFor +
screen.getByTestId and the jest-dom matchers to check visibility; target the
test wrapping EditSponsorPage and the simple-tabpanel-* test ids.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c4adf3 and c3e31c6.

📒 Files selected for processing (4)
  • src/pages/sponsors/__tests__/edit-sponsor-page.test.js
  • src/pages/sponsors/edit-sponsor-page.js
  • src/reducers/sponsors/sponsor-reducer.js
  • src/reducers/summits/current-summit-reducer.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/sponsors/edit-sponsor-page.js
🧰 Additional context used
🧬 Code graph analysis (2)
src/reducers/sponsors/sponsor-reducer.js (15)
src/reducers/summits/current-summit-reducer.js (2)
  • DEFAULT_STATE (217-223)
  • DEFAULT_STATE (217-223)
src/reducers/payment_profiles/payment-profile-reducer.js (1)
  • DEFAULT_STATE (39-42)
src/reducers/sponsors/sponsor-customized-form-reducer.js (1)
  • DEFAULT_STATE (41-43)
src/reducers/sponsors/sponsor-page-forms-list-reducer.js (1)
  • DEFAULT_STATE (29-51)
src/reducers/sponsors/sponsor-list-reducer.js (1)
  • DEFAULT_STATE (25-34)
src/reducers/badges/badge-feature-list-reducer.js (1)
  • DEFAULT_STATE (23-28)
src/reducers/sponsors/sponsor-social-network-reducer.js (1)
  • DEFAULT_STATE (35-38)
src/reducers/sponsors/sponsor-material-reducer.js (1)
  • DEFAULT_STATE (36-39)
src/reducers/badges/badge-type-reducer.js (1)
  • DEFAULT_STATE (42-45)
src/reducers/sponsors/sponsor-advertisement-reducer.js (1)
  • DEFAULT_STATE (39-42)
src/reducers/badges/access-level-list-reducer.js (1)
  • DEFAULT_STATE (23-28)
src/reducers/badges/badge-type-list-reducer.js (1)
  • DEFAULT_STATE (23-28)
src/reducers/badges/access-level-reducer.js (1)
  • DEFAULT_STATE (34-37)
src/reducers/badges/badge-feature-reducer.js (1)
  • DEFAULT_STATE (36-39)
src/reducers/refund_policies/refund-policy-list-reducer.js (1)
  • DEFAULT_STATE (25-30)
src/reducers/summits/current-summit-reducer.js (16)
src/reducers/events/summit-event-reducer.js (1)
  • DEFAULT_STATE (108-115)
src/reducers/tickets/ticket-list-reducer.js (1)
  • DEFAULT_STATE (28-45)
src/reducers/tickets/ticket-reducer.js (1)
  • DEFAULT_STATE (63-67)
src/reducers/attendees/attendee-reducer.js (1)
  • DEFAULT_STATE (56-59)
src/reducers/promocodes/promocode-reducer.js (1)
  • DEFAULT_STATE (78-83)
src/reducers/tickets/ticket-type-reducer.js (1)
  • DEFAULT_STATE (42-45)
src/reducers/events/event-type-reducer.js (1)
  • DEFAULT_STATE (62-65)
src/reducers/locations/location-list-reducer.js (1)
  • DEFAULT_STATE (24-28)
src/reducers/locations/room-reducer.js (1)
  • DEFAULT_STATE (47-50)
src/reducers/events/event-category-list-reducer.js (1)
  • DEFAULT_STATE (25-27)
src/reducers/events/event-category-reducer.js (1)
  • DEFAULT_STATE (51-55)
src/reducers/speakers/speaker-reducer.js (1)
  • DEFAULT_STATE (52-55)
src/reducers/events/event-material-reducer.js (1)
  • DEFAULT_STATE (42-45)
src/reducers/badges/badge-type-reducer.js (1)
  • DEFAULT_STATE (42-45)
src/reducers/locations/floor-reducer.js (1)
  • DEFAULT_STATE (37-40)
src/reducers/locations/location-reducer.js (1)
  • DEFAULT_STATE (67-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (2)
src/reducers/sponsors/sponsor-reducer.js (1)

135-139: LGTM — exporting DEFAULT_STATE is consistent and safe.
This aligns with the repo pattern for reducer default state access.

src/reducers/summits/current-summit-reducer.js (1)

217-223: LGTM — DEFAULT_STATE export is a clean consistency update.
No behavioral impact, just improved reuse.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +144 to +151
const usersTabReference = screen.getByText("edit_sponsor.tab.forms");

await act(async () => {
await userEvent.click(usersTabReference);
});

expect(window.location.hash).toBe("forms");
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find and read the test file
cat -n src/pages/sponsors/__tests__/edit-sponsor-page.test.js | head -200

Repository: fntechgit/summit-admin

Length of output: 6883


🏁 Script executed:

# Find and read the EditSponsorPage component
cat -n src/pages/sponsors/edit-sponsor-page.js | head -300

Repository: fntechgit/summit-admin

Length of output: 11190


Fix hash format to match JSDOM behavior.
window.location.hash always includes the leading # when accessed in real JSDOM. The component assigns "forms" (line 129 of edit-sponsor-page.js calls getFragmentFromValue() which returns fragment values without #), but the test assertion should expect the actual stored value with the # prefix.

🔧 Suggested fix
-      expect(window.location.hash).toBe("forms");
+      expect(window.location.hash).toBe("#forms");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const usersTabReference = screen.getByText("edit_sponsor.tab.forms");
await act(async () => {
await userEvent.click(usersTabReference);
});
expect(window.location.hash).toBe("forms");
});
const usersTabReference = screen.getByText("edit_sponsor.tab.forms");
await act(async () => {
await userEvent.click(usersTabReference);
});
expect(window.location.hash).toBe("#forms");
});
🤖 Prompt for AI Agents
In `@src/pages/sponsors/__tests__/edit-sponsor-page.test.js` around lines 144 -
151, The test's assertion expects window.location.hash without the leading '#'
but JSDOM stores hashes with the '#' prefix; update the assertion in
src/pages/sponsors/__tests__/edit-sponsor-page.test.js to expect "#forms"
instead of "forms" (the component sets the fragment via getFragmentFromValue()
in edit-sponsor-page.js which returns values without '#', but
window.location.hash will include it). Ensure any other assertions in this test
that check window.location.hash are adjusted the same way.

@niko-exo niko-exo requested a review from smarcet January 16, 2026 12:24
@niko-exo
Copy link
Author

Ready for review @smarcet

@niko-exo niko-exo force-pushed the feature/tab-deep-linking-fragment branch from c3e31c6 to f805edb Compare January 20, 2026 19:25
@niko-exo
Copy link
Author

Rebase branch

Copy link

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@smarcet smarcet merged commit 7bc030e into master Jan 20, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants